//========= Copyright ?1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//

#if defined( _WIN32 ) && !defined( _X360 )
#define WIN_32_LEAN_AND_MEAN
#include <windows.h>
#define VA_COMMIT_FLAGS MEM_COMMIT
#define VA_RESERVE_FLAGS MEM_RESERVE
#elif defined( _X360 )
#define VA_COMMIT_FLAGS (MEM_COMMIT|MEM_NOZERO|MEM_LARGE_PAGES)
#define VA_RESERVE_FLAGS (MEM_RESERVE|MEM_LARGE_PAGES)
#endif

#include <tier0/dbg.h>
#include "tier1/memstack.h"
#include "tier1/utlmap.h"
#include <tier0/memdbgon.h>

#ifdef _WIN32
#pragma warning(disable:4073)
#pragma init_seg(lib)
#endif

//-----------------------------------------------------------------------------

MEMALLOC_DEFINE_EXTERNAL_TRACKING(CMemoryStack);

//-----------------------------------------------------------------------------

CMemoryStack::CMemoryStack()
	: m_pBase(NULL),
	m_pNextAlloc(NULL),
	m_pAllocLimit(NULL),
	m_pCommitLimit(NULL),
	m_alignment(16),
#if defined(_WIN32)
	m_commitSize(0),
	m_minCommit(0),
#endif
	m_maxSize(0)
{
}

//-------------------------------------

CMemoryStack::~CMemoryStack()
{
	if (m_pBase)
		Term();
}

//-------------------------------------

bool CMemoryStack::Init(unsigned maxSize, unsigned commitSize, unsigned initialCommit, unsigned alignment)
{
	Assert(!m_pBase);

#ifdef _X360
	m_bPhysical = false;
#endif

	m_maxSize = maxSize;
	m_alignment = AlignValue(alignment, 4);

	Assert(m_alignment == alignment);
	Assert(m_maxSize > 0);

#if defined(_WIN32)
	if (commitSize != 0)
	{
		m_commitSize = commitSize;
	}

	unsigned pageSize;

#ifndef _X360
	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	Assert(!(sysInfo.dwPageSize & (sysInfo.dwPageSize - 1)));
	pageSize = sysInfo.dwPageSize;
#else
	pageSize = 64 * 1024;
#endif

	if (m_commitSize == 0)
	{
		m_commitSize = pageSize;
	}
	else
	{
		m_commitSize = AlignValue(m_commitSize, pageSize);
	}

	m_maxSize = AlignValue(m_maxSize, m_commitSize);

	Assert(m_maxSize % pageSize == 0 && m_commitSize % pageSize == 0 && m_commitSize <= m_maxSize);

	m_pBase = (unsigned char *)VirtualAlloc(NULL, m_maxSize, VA_RESERVE_FLAGS, PAGE_NOACCESS);
	Assert(m_pBase);
	m_pCommitLimit = m_pNextAlloc = m_pBase;

	if (initialCommit)
	{
		initialCommit = AlignValue(initialCommit, m_commitSize);
		Assert(initialCommit < m_maxSize);
		if (!VirtualAlloc(m_pCommitLimit, initialCommit, VA_COMMIT_FLAGS, PAGE_READWRITE))
			return false;
		m_minCommit = initialCommit;
		m_pCommitLimit += initialCommit;
		MemAlloc_RegisterExternalAllocation(CMemoryStack, GetBase(), GetSize());
	}

#else
	m_pBase = MemAlloc_AllocAligned(m_maxSize, alignment ? alignment : 1);
	m_pNextAlloc = m_pBase;
	m_pCommitLimit = m_pBase + m_maxSize;
#endif

	m_pAllocLimit = m_pBase + m_maxSize;

	return (m_pBase != NULL);
}

//-------------------------------------

#ifdef _X360
bool CMemoryStack::InitPhysical(unsigned size, unsigned alignment)
{
	m_bPhysical = true;

	m_maxSize = m_commitSize = size;
	m_alignment = AlignValue(alignment, 4);

	int flags = PAGE_READWRITE;
	if (size >= 16 * 1024 * 1024)
	{
		flags |= MEM_16MB_PAGES;
	}
	else
	{
		flags |= MEM_LARGE_PAGES;
	}
	m_pBase = (unsigned char *)XPhysicalAlloc(m_maxSize, MAXULONG_PTR, 4096, flags);
	Assert(m_pBase);
	m_pNextAlloc = m_pBase;
	m_pCommitLimit = m_pBase + m_maxSize;
	m_pAllocLimit = m_pBase + m_maxSize;

	MemAlloc_RegisterExternalAllocation(CMemoryStack, GetBase(), GetSize());
	return (m_pBase != NULL);
}
#endif

//-------------------------------------

void CMemoryStack::Term()
{
	FreeAll();
	if (m_pBase)
	{
#if defined(_WIN32)
		VirtualFree(m_pBase, 0, MEM_RELEASE);
#else
		MemAlloc_FreeAligned(m_pBase);
#endif
		m_pBase = NULL;
	}
}

//-------------------------------------

int CMemoryStack::GetSize()
{
#ifdef _WIN32
	return m_pCommitLimit - m_pBase;
#else
	return m_maxSize;
#endif
}


//-------------------------------------

bool CMemoryStack::CommitTo(byte *pNextAlloc)
{
#ifdef _X360
	if (m_bPhysical)
	{
		return NULL;
	}
#endif
#if defined(_WIN32)
	unsigned char *	pNewCommitLimit = AlignValue(pNextAlloc, m_commitSize);
	unsigned 		commitSize = pNewCommitLimit - m_pCommitLimit;

	if (GetSize())
		MemAlloc_RegisterExternalDeallocation(CMemoryStack, GetBase(), GetSize());

	if (m_pCommitLimit + commitSize > m_pAllocLimit)
	{
		return false;
	}

	if (!VirtualAlloc(m_pCommitLimit, commitSize, VA_COMMIT_FLAGS, PAGE_READWRITE))
	{
		Assert(0);
		return false;
	}
	m_pCommitLimit = pNewCommitLimit;

	if (GetSize())
		MemAlloc_RegisterExternalAllocation(CMemoryStack, GetBase(), GetSize());
	return true;
#else
	Assert(0);
	return false;
#endif
}

//-------------------------------------

void CMemoryStack::FreeToAllocPoint(MemoryStackMark_t mark, bool bDecommit)
{
	void *pAllocPoint = m_pBase + mark;
	Assert(pAllocPoint >= m_pBase && pAllocPoint <= m_pNextAlloc);

	if (pAllocPoint >= m_pBase && pAllocPoint < m_pNextAlloc)
	{
		if (bDecommit)
		{
#if defined(_WIN32)
			unsigned char *pDecommitPoint = AlignValue((unsigned char *)pAllocPoint, m_commitSize);

			if (pDecommitPoint < m_pBase + m_minCommit)
			{
				pDecommitPoint = m_pBase + m_minCommit;
			}

			unsigned decommitSize = m_pCommitLimit - pDecommitPoint;

			if (decommitSize > 0)
			{
				MemAlloc_RegisterExternalDeallocation(CMemoryStack, GetBase(), GetSize());

				VirtualFree(pDecommitPoint, decommitSize, MEM_DECOMMIT);
				m_pCommitLimit = pDecommitPoint;

				if (mark > 0)
				{
					MemAlloc_RegisterExternalAllocation(CMemoryStack, GetBase(), GetSize());
				}
			}
#endif
		}
		m_pNextAlloc = (unsigned char *)pAllocPoint;
	}
}

//-------------------------------------

void CMemoryStack::FreeAll(bool bDecommit)
{
	if (m_pBase && m_pCommitLimit - m_pBase > 0)
	{
		if (bDecommit)
		{
#if defined(_WIN32)
			MemAlloc_RegisterExternalDeallocation(CMemoryStack, GetBase(), GetSize());

			VirtualFree(m_pBase, m_pCommitLimit - m_pBase, MEM_DECOMMIT);
			m_pCommitLimit = m_pBase;
#endif
		}
		m_pNextAlloc = m_pBase;
	}
}

//-------------------------------------

void CMemoryStack::Access(void **ppRegion, unsigned *pBytes)
{
	*ppRegion = m_pBase;
	*pBytes = (m_pNextAlloc - m_pBase);
}

//-------------------------------------

void CMemoryStack::PrintContents()
{
	Msg("Total used memory:      %d\n", GetUsed());
	Msg("Total committed memory: %d\n", GetSize());
}

//-----------------------------------------------------------------------------
